{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Common problems and pitfalls"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Regular Vector Fitting is susceptible to some general problems and to specific user errors. This section is intended to collect and address common issues and to help mitigating them. Additional explanations and background information can be found in the [Vector Fitting tutorial](../../tutorials/VectorFitting.ipynb)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sources of problems"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following things can cause significant problems in the vector fitting process and may result in poor convergence and/or large errors of the fit:\n",
    "1. not enough poles\n",
    "2. too many poles\n",
    "3. strong noise in the data\n",
    "\n",
    "From points 1) and 2) it is obvious that the number of poles has to be chosen carefully, which often requires several attempts to get right. But even with the right number of poles, the fit quality and the required number of iterations can be disappointing in case of strong noise in the data, usually from measurements. In such cases, successful vector fits might still be achieved with `auto_fit()`, an improved and automated version of the vector fitting algorithm utilizing iterative pole adding and skimming.\n",
    "\n",
    "The 2-port measurement from example 2 is a tricky network to fit properly. It will serve as an example of the different issues. Also see [Ex2: Measured 190 GHz Active 2-Port](vectorfitting_ex2_190ghz_active.ipynb) for a demonstration of `auto_fit()` on this network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as mplt\n",
    "import numpy as np\n",
    "\n",
    "import skrf\n",
    "\n",
    "nw = skrf.network.Network('./190ghz_tx_measured.S2P')\n",
    "vf = skrf.VectorFitting(nw)\n",
    "freqs = np.linspace(100e9, 300e9, 401)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example of noisy data with not enough poles"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Three real poles and one complex conjugate pair is not enough for an acurrate fit of this network. It takes surprisingly many iterations and the resulting model error is still fairly large:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.vector_fit(n_poles_real=3, n_poles_cmplx=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f'RMS error = {vf.get_rms_error()}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.plot_convergence()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.plot_s_db(freqs=freqs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example of noisy data with too many poles"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ten real poles and ten complex conjugate pairs are too much for this simple network. The unsued poles get shifted toward higher frequencies outside the measured band and still spook around during the relocation process. Due to these spurious pole, the relocation process cannot converge properly and gets stopped after reaching the maxmimum number of iterations (default is 100). Interstingly, the residue settles rather quickly within the first 20 iterations, as shown in the convergence plot.\n",
    "Still, the fitting result within the measured frequency band is very good, but the spurious out-of-band poles are clearly visible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.vector_fit(n_poles_real=10, n_poles_cmplx=10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.plot_convergence()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f'RMS error = {vf.get_rms_error()}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.plot_s_db(freqs=freqs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comment on starting poles"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "During the pole relocation process (first step in the fitting process), the starting poles are sucessively moved to frequencies where they can best match all target responses. Additionally, the type of poles can change from real to complex-conjugate: two real poles can become one complex-conjugate pole (and vise versa). As a result, there are multiple combinations of starting poles which can produce the same final set of poles. However, certain setups will converge faster than others, which also depends on the initial pole spacing. In extreme cases, the algorithm can even be \"undecided\" if two real poles behave exactly like one complex-conjugate pole and it gets \"stuck\" jumping back and forth without converging to a final solution.\n",
    "\n",
    "Equivalent setups for a fitting attempt with `n_poles_real=3, n_poles_cmplx=4` (i.e. 3+4):\n",
    "\n",
    "- 1+5\n",
    "- **3+4**\n",
    "- 5+3\n",
    "- 7+2\n",
    "- 9+1\n",
    "- 11+0\n",
    "\n",
    "Equivalent setups for another fitting attempt with `n_poles_real=4, n_poles_cmplx=4` (i.e. 4+4):\n",
    "\n",
    "- 0+6\n",
    "- 2+5\n",
    "- **4+4**\n",
    "- 6+3\n",
    "- 8+2\n",
    "- 10+1\n",
    "- 12+0\n",
    "\n",
    "Examples for problematic setups that do not converge properly due to an oscillation between two (equally good) solutions:\n",
    "\n",
    "- 0+5 <--> 2+4 <--> ...\n",
    "- 0+7 <--> 2+5 <--> ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.vector_fit(n_poles_real=0, n_poles_cmplx=5)\n",
    "vf.plot_convergence()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even though the pole relocation process oscillated between two (or more?) solutions and did not converge, the fit was still successful, because the solutions themselves did converge:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vf.get_rms_error()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = mplt.subplots(2, 2)\n",
    "fig.set_size_inches(12, 8)\n",
    "vf.plot_s_mag(0, 0, freqs=freqs, ax=ax[0][0]) # s11\n",
    "vf.plot_s_mag(0, 1, freqs=freqs, ax=ax[0][1]) # s12\n",
    "vf.plot_s_mag(1, 0, freqs=freqs, ax=ax[1][0]) # s21\n",
    "vf.plot_s_mag(1, 1, freqs=freqs, ax=ax[1][1]) # s22\n",
    "fig.tight_layout()\n",
    "mplt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.2"
  },
  "nbsphinx": {
   "timeout": 360
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
